package net.pimpas.executor.serviceprovider.impl;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.executor.ExecutorThreadType;
import net.pimpas.executor.serviceprovider.ExecutorServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;

@ServiceProviderAnnotation(
		instanceType=ServiceProviderInstanceType.NORMAL,
		serviceProviderType=ExecutorServiceProvider.class)
		public class ExecutorServiceProviderImpl implements ExecutorServiceProvider {

	private String name;
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(ExecutorServiceProviderImpl.class);
	private ExecutorService service;
	private int fixedThreadPoolNumber;
	private ThreadFactory threadFactory;
	private ExecutorThreadType threadType;
	private volatile boolean configured;
	private int numberThread;

	public ExecutorServiceProviderImpl(String name) {
		this.setName(name);
		this.setConfigured(false);
	}

	public void config() throws ConfigurationException {
		logger.info("[ExecutorServiceProvider]: Configuring ExecutorProvider for ["+name+"]");
	}

	private void configureService( ) {
		if(this.getService() != null)
			throw new IllegalStateException("Cannot replace ExecutorService!");
		switch(this.getThreadType()) {
			case SINGLE_THREAD:
				this.setService((this.getThreadFactory()==null) ?
						Executors.newSingleThreadExecutor() : 
							Executors.newSingleThreadExecutor(this.getThreadFactory()));
				break;
	
			case FIXED_THREAD_POOL:
				this.setService((this.getThreadFactory() == null) ? 
						Executors.newFixedThreadPool(this.getNumberThread()) : 
							Executors.newFixedThreadPool(this.getNumberThread(), this.getThreadFactory()));
				break;
	
			case CACHED_THREAD_POOL:
				this.setService((this.getThreadFactory() == null) ?
						Executors.newCachedThreadPool() :
							Executors.newCachedThreadPool(this.getThreadFactory()));
				break;
	
			case SCHEDULE_THREAD_POOL:
				this.setService((this.getThreadFactory() == null) ?
						Executors.newScheduledThreadPool(this.getNumberThread()) :
							Executors.newScheduledThreadPool(this.getNumberThread(), this.getThreadFactory()));
				break;
		}
	}

	private void configure(ExecutorThreadType type, boolean direct)
	throws ConfigurationException {
		if(this.isConfigured())
			throw new ConfigurationException("Already configured!");
		this.setThreadType(type);
		if(direct) {
			this.setThreadType(ExecutorThreadType.SINGLE_THREAD);
			this.configureService();
		}
		
		this.setConfigured(true);
	}

	public void configure(ExecutorThreadType type)
	throws ConfigurationException {
		this.configure(type, true);
	}

	public void configure(ExecutorThreadType type, int number)
	throws ConfigurationException {
		assert(number>0);

		this.configure(type, false);
		this.setNumberThread(number);
		
		this.configureService();
	}

	public void configure(ExecutorThreadType type, int number,
			ThreadFactory threadFactory) throws ConfigurationException {
		assert(number>0);

		this.configure(type, false);
		this.setNumberThread(number);
		this.setThreadFactory(threadFactory);
		
		this.configureService();
	}

	public void configure(ExecutorThreadType type, ThreadFactory threadFactory)
	throws ConfigurationException {
		assert(threadFactory!=null);

		this.configure(type);
		this.setThreadFactory(threadFactory);
		
		this.configureService();
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public ExecutorService getService() {
		return service;
	}

	public void setService(ExecutorService service) {
		this.service = service;
	}

	public int getFixedThreadPoolNumber() {
		return fixedThreadPoolNumber;
	}

	public void setFixedThreadPoolNumber(int fixedThreadPoolNumber) {
		this.fixedThreadPoolNumber = fixedThreadPoolNumber;
	}

	public ThreadFactory getThreadFactory() {
		return threadFactory;
	}

	public void setThreadFactory(ThreadFactory threadFactory) {
		this.threadFactory = threadFactory;
	}

	public ExecutorThreadType getThreadType() {
		return threadType;
	}

	public void setThreadType(ExecutorThreadType threadType) {
		this.threadType = threadType;
	}

	public boolean isConfigured() {
		return configured;
	}

	public void setConfigured(boolean configured) {
		this.configured = configured;
	}

	public int getNumberThread() {
		return numberThread;
	}

	public void setNumberThread(int numberThread) {
		this.numberThread = numberThread;
	}

	/*
	 * Delegate
	 */
	public boolean awaitTermination(long timeout, TimeUnit unit)
	throws InterruptedException {
		return service.awaitTermination(timeout, unit);
	}

	public void execute(Runnable command) {
		service.execute(command);
	}

	public <T> List<Future<T>> invokeAll(
			Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
			throws InterruptedException {
		return service.invokeAll(tasks, timeout, unit);
	}

	public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
	throws InterruptedException {
		return service.invokeAll(tasks);
	}

	public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
			long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		return service.invokeAny(tasks, timeout, unit);
	}

	public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
	throws InterruptedException, ExecutionException {
		return service.invokeAny(tasks);
	}

	public boolean isShutdown() {
		return service.isShutdown();
	}

	public boolean isTerminated() {
		return service.isTerminated();
	}

	public void shutdown() {
		service.shutdown();
	}

	public List<Runnable> shutdownNow() {
		return service.shutdownNow();
	}

	public <T> Future<T> submit(Callable<T> task) {
		return service.submit(task);
	}

	public <T> Future<T> submit(Runnable task, T result) {
		return service.submit(task, result);
	}

	public Future<?> submit(Runnable task) {
		return service.submit(task);
	}
}